package com.spring;


import com.spring.Annotation.Autowired;
import com.spring.Annotation.Component;
import com.spring.Annotation.ComponentScan;
import com.spring.Annotation.Scope;
import com.spring.Exception.NoSuchBeanDefinedException;
import com.spring.Interface.BeanNameAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Classname MyApplicationContext
 * @Description TODO
 * @Date 2021/6/27 22:28
 * @Created by ASUS
 */
public class MyApplicationContext {

    private static final Logger logger = LoggerFactory.getLogger(MyApplicationContext.class);

    private static final String SINGLETON = "singleton";

    private static ConcurrentHashMap<String, BeanDefination> beanDefinitionMap = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, Object> singletonMap = new ConcurrentHashMap<>();

    private Class configClass;

    public MyApplicationContext(Class configClass) throws IOException {
        this.configClass = configClass;

        // 拿到该配置类，解析该类，解析该类上定义的spring的注解
        ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();// 获取扫描路径
        // 扫描需要类加载器
        // bootstrap---->jre/lib
        // Ext---->jre/ext/lib
        // App----->classpath
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(path.replace('.', '/'));
        assert resource != null;
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            for (File f : files) {
                logger.info(f.getAbsolutePath());
                if (f.getAbsolutePath().endsWith(".class")) {
                    String absolutePath = f.getAbsolutePath();
                    String className = getClassNameFromPath(path, absolutePath);
                    try {

                        Class<?> clazz = classLoader.loadClass(className);
                        assert clazz != null;

                        if (clazz.isAnnotationPresent(Component.class)) {
                            Component component = clazz.getDeclaredAnnotation(Component.class);
                            String beanName = component.value();//获取beanName
                            BeanDefination beanDefination = new BeanDefination();// 创建beanDefinition
                            beanDefination.setClazz(clazz);
                            if (clazz.isAnnotationPresent(Scope.class)) {// 设置Bean的作用域
                                Scope scope = clazz.getDeclaredAnnotation(Scope.class);
                                beanDefination.setScope(scope.value());
                            }
                            beanDefinitionMap.put(beanName, beanDefination);
                        }

                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        createSingletonBean();


    }

    @SuppressWarnings("unchecked")
    private void createSingletonBean() {// 创建单例池对象
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefination beanDefination = beanDefinitionMap.get(beanName);
            if (beanDefination.getScope().equals(SINGLETON)) {
                Class clazz = beanDefination.getClazz();
                try {
                    Object instance = clazz.getConstructor().newInstance();

                    for (Field declaredField : clazz.getDeclaredFields()) {
                        declaredField.setAccessible(true);
                        if (declaredField.isAnnotationPresent(Autowired.class)) {
                            String fieldName = declaredField.getName();
                            if (!beanDefinitionMap.containsKey(fieldName)) throw new NoSuchBeanDefinedException();
                            BeanDefination fieldDefinition = beanDefinitionMap.get(fieldName);
                            Object fieldObject = fieldDefinition.getClazz().getConstructor().newInstance();
                            declaredField.set(instance, fieldObject);
                        }
                    }
                    if (instance instanceof BeanNameAware) {
                        ((BeanNameAware) instance).setBeanName(beanName);
                    }
                    singletonMap.put(beanName, instance);// 添加对象到单例池
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    @SuppressWarnings("unchecked")
    private Object createBean(String beanName) {
        if (!beanDefinitionMap.containsKey(beanName)) throw new NoSuchBeanDefinedException();
        if (singletonMap.containsKey(beanName)) return singletonMap.get(beanName);
        BeanDefination beanDefination = beanDefinitionMap.get(beanName);
        try {
            Class clazz = beanDefination.getClazz();
            Object instance = clazz.getConstructor().newInstance();
            for (Field declaredField : clazz.getDeclaredFields()) {
                declaredField.setAccessible(true);
                String fieldName = declaredField.getName();
                Object fieldObject = null;
                if (singletonMap.containsKey(fieldName)) {
                    fieldObject = singletonMap.get(fieldName);
                } else {
                    if (!beanDefinitionMap.containsKey(fieldName)) {
                        throw new NoSuchBeanDefinedException("Filed property bean is not defined in MySpring....");
                    }
                    fieldObject = beanDefinitionMap.get(fieldName).getClazz().getConstructor().newInstance();
                }
                declaredField.set(instance, fieldObject);
            }
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getClassNameFromPath(String path, String absolutePath) {
        String[] split = absolutePath.split("\\\\");
        String tmp = split[split.length - 1].split("\\.")[0];
        return path + "." + tmp;
    }

    public Object getBean(String beanName) {
        if (!beanDefinitionMap.containsKey(beanName)) throw new NoSuchBeanDefinedException();
        if (singletonMap.containsKey(beanName)) {
            return singletonMap.get(beanName);
        }
        return createBean(beanName);
    }
}
